/*
 * Copyright (c) 2022-2023, Arm Limited and Contributors. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

#include "mbed_critical/mbed_critical.h"
#include "pw_assert/check.h"
#include "pw_iot_sdk_config/config.h"
#include "pw_span/span.h"
#include "pw_status/status.h"
#include "pw_sys_io/sys_io.h"
#include "pw_sys_io_cmsis_driver/backend.h"
#include "pw_sys_io_cmsis_driver/config.h"

#include <algorithm>
#include <atomic>
#include <cassert>
#include <cinttypes>
#include <climits>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <span>
#include <type_traits>

extern "C" {
#include "Driver_USART.h"

void _pw_sys_io_lock_init(void);
void _pw_sys_io_lock_read(void);
void _pw_sys_io_unlock_read(void);
void _pw_sys_io_lock_write(void);
void _pw_sys_io_unlock_write(void);
}

namespace {

template <void (*lock)(), void (*unlock)()> struct LockGuard {
    LockGuard()
    {
        lock();
    }

    LockGuard(const LockGuard &) = delete;
    LockGuard &operator=(const LockGuard &) = delete;

    ~LockGuard()
    {
        unlock();
    }
};

using CriticalSection = LockGuard<core_util_critical_section_enter, core_util_critical_section_exit>;
using RxLockGuard = LockGuard<_pw_sys_io_lock_read, _pw_sys_io_unlock_read>;
using TxLockGuard = LockGuard<_pw_sys_io_lock_write, _pw_sys_io_unlock_write>;

} // namespace

ARM_DRIVER_USART *serial_;

extern "C" void pw_sys_io_init(ARM_DRIVER_USART *serial)
{
    serial_ = serial;
    _pw_sys_io_lock_init();
}

namespace pw::sys_io {
Status ReadByte(std::byte *dest)
{
    RxLockGuard _;

    while (true) {
        Status status = TryReadByte(dest);

        // Return status when it's OK or an actual error.
        if (status.code() != PW_STATUS_UNAVAILABLE) {
            return status;
        }
    }
}

Status TryReadByte(std::byte *dest)
{
    RxLockGuard _;
    if (0 == serial_->Receive(dest, 1)) {
        return OkStatus();
    }

    return Status::Unavailable();
}

StatusWithSize ReadBytes(ByteSpan dest)
{
    RxLockGuard _;
    size_t count = serial_->Receive(dest.data(), dest.size());
    return StatusWithSize(OkStatus(), count);
}

Status WriteByte(std::byte b)
{
    TxLockGuard _;
    // Note: the API expects to block if the serial is not yet available.
    while (0 != serial_->Send(&b, 1))
        ;
    return OkStatus();
}

StatusWithSize WriteBytes(ConstByteSpan data)
{
    TxLockGuard _;
    serial_->Send(data.data(), data.size());
    return StatusWithSize(OkStatus(), data.size());
}

StatusWithSize WriteLine(const std::string_view &s)
{
    TxLockGuard _;
    WriteBytes(ConstByteSpan(reinterpret_cast<const std::byte *>(s.data()), s.size()));
    WriteByte(std::byte('\r'));
    WriteByte(std::byte('\n'));

    return StatusWithSize(OkStatus(), s.size() + 2);
}
} // namespace pw::sys_io
